/*--------------------------------------------------------------------------
 *
 * 	You may freely copy, distribute, and reuse the code in this example.
 * 	SHL Systemhouse disclaims any warranty of any kind, expressed or  
 *	implied, as to its fitness for any particular use.
 *
 *
 *	QueryController
 *
 *	Inherits From:		NSObject
 *
 *	Conforms To:		None.
 *
 *	Declared In:		QueryController.h
 *
 *
 *------------------------------------------------------------------------*/
#import "QueryController.h"
#import "EOQualifierContentsCategory.h"




@implementation QueryController


/*--------------------------------------------------------------------------
 *	Model file reading and preparation for query building
 *------------------------------------------------------------------------*/
- _setUpForModelFile: (NSString *)aFilepath
{
	id model = [[EOModel allocWithZone: NULL]
				initWithContentsOfFile: aFilepath];

	/*----------------------------------------------------------------------
	 *	create adaptor with specifics contained in the model (server, etc.)
	 *--------------------------------------------------------------------*/
	if (adaptor) [adaptor autorelease];
	adaptor = [[EOAdaptor adaptorWithModel: model] retain];
	[model autorelease];

	/*----------------------------------------------------------------------
 	 *	set up database pieces to allow transactions, qualifiers, etc.
	 *--------------------------------------------------------------------*/
	if (database) [database autorelease];
	database = [[EODatabase allocWithZone: [self zone]]
					initWithAdaptor: adaptor];

	if (dbContext) [dbContext autorelease];
	dbContext = [[EODatabaseContext alloc] initWithDatabase: database];
	
	if (dbChannel) [dbChannel autorelease];
	dbChannel = [[EODatabaseChannel alloc] initWithDatabaseContext: dbContext];

	/*----------------------------------------------------------------------
	 *	open the channel and turn on debug to see SQL in console
	 *--------------------------------------------------------------------*/
	if (! [dbChannel openChannel]) {
		NSLog(@"database channel could not be opened.");
	}
	[[dbChannel adaptorChannel] setDebugEnabled:YES];

	entity = nil;

	[self clearQualifier:nil];
	[[qualifierBrowser matrixInColumn:0] renewRows:0 cols:0];
	[[qualifierBrowser matrixInColumn:0] sizeToCells];
	[qualifierBrowser display];
	return self;
}


/*--------------------------------------------------------------------------
 *	IconWell delegate method
 *------------------------------------------------------------------------*/
- dragSource:dragSource didDropOnIconWell:sender
{
	[modelPath setStringValue: (const char *)[sender path]];
	[self _setUpForModelFile: [NSString stringWithCString:
		(char *)[sender path]]];

	[[browser loadColumnZero] display];
	[window makeKeyAndOrderFront: self];

	return self;
}


/*--------------------------------------------------------------------------
 *	Application delegation
 *------------------------------------------------------------------------*/
- appDidInit: sender
{
	qualifier = nil;
	dataSource = nil;
	controller = nil;
	dbContext = nil;
	dbChannel = nil;

	[qualifierOperation setEmptySelectionEnabled:YES];
	[qualifierOperation selectCellAt:-1:-1];
	[window makeKeyAndOrderFront: self];
	return self;
}


/*--------------------------------------------------------------------------
 *	Query Actions
 *------------------------------------------------------------------------*/
- performQuery:sender
{
	id 			selection = [[NSMutableArray allocWithZone:NULL] init];
	id			selectedCells = [[List alloc] init];
	int			countSelected, iterator;
	const char 	*cString;
	
	/*----------------------------------------------------------------------
	 *	get attribute names to be selected and browsed
	 *--------------------------------------------------------------------*/
	[[browser matrixInColumn:1] getSelectedCells:selectedCells];
	countSelected = [selectedCells count];

	if (countSelected == 0) {
		[selectedCells free];
		selectedCells = [[browser matrixInColumn:1] cellList];
		countSelected = [selectedCells count];
	}
	
	/*----------------------------------------------------------------------
	 *	transfer info from a NS3.2 list to a NS3.3NSArray
	 *--------------------------------------------------------------------*/
	for (iterator = 0; iterator < countSelected; iterator++) {
		cString = [(Cell *)[selectedCells objectAt: iterator] stringValue];
		[(NSMutableArray *)selection addObject:
				[NSString stringWithCString: cString]];
	}

	/*----------------------------------------------------------------------
	 *	assure current entity
	 *--------------------------------------------------------------------*/
	cString = [(Cell *)[[browser matrixInColumn:0] selectedCell] stringValue];
	entity = [[adaptor model] entityNamed:
				[NSString stringWithCString: cString]];

	/*----------------------------------------------------------------------
	 *	if qualifier is not defined, create an unqualified fetch.
	 *--------------------------------------------------------------------*/
	if (qualifier == nil)
		qualifier = [entity qualifier];

	/*----------------------------------------------------------------------
	 *	call method to handle fetch and reporting thru NXTableView
	 *--------------------------------------------------------------------*/
	[self fetchAndDisplay:selection inEntity:entity
				withQualifier:qualifier];

    return self;
}


- (void) fetchAndDisplay:(NSArray *)attributes inEntity:(EOEntity *)anEntity
	withQualifier:(EOQualifier *)aQualifier
{
	unsigned int	count, iterator;

	[resultWindow disableDisplay];

	/*----------------------------------------------------------------------
	 *	if not the first query, we need to change dataSources.  The entity
	 *	may be different from the previous query.
	 *--------------------------------------------------------------------*/
	if (dataSource) [dataSource autorelease];

	dataSource = [[EODatabaseDataSource allocWithZone:[self zone]]
					initWithDatabaseChannel:dbChannel
					entityNamed:[anEntity name]];
	/*----------------------------------------------------------------------
	 *	get a new controller with the new dataSource
	 *--------------------------------------------------------------------*/
	controller = [[EOController allocWithZone: [self zone]]
			initWithDataSource: dataSource];

	/*----------------------------------------------------------------------
	 *	delete all current columns in the table view
	 *--------------------------------------------------------------------*/
	count = [resultTable columnCount];
	for (iterator = count; iterator > 0; iterator--)
		[resultTable removeColumnAt: iterator-1];
	[(NXTableView *)resultTable setDataSource:nil];

	/*----------------------------------------------------------------------
	 *	iterate through the desired select attributes and build columns for
	 *	each in the tableView.
	 *--------------------------------------------------------------------*/
	count = [attributes count];
	for (iterator = 0; iterator < count; iterator ++) {
		id	association;
		id	attribute = [anEntity attributeNamed:
			[attributes objectAtIndex:iterator]];

		[resultTable addColumn:attribute withTitle:
			[[(EOAttribute *)attribute name] cString]];

	/*----------------------------------------------------------------------
	 *	create a new association for each of these attribute/column pairs
	 *--------------------------------------------------------------------*/
		association = [[EOColumnAssociation allocWithZone: [self zone]]
			initWithController:controller key: [(EOAttribute *)attribute name]
			destination:[resultTable columnAt:iterator]];

		[association setTableView:resultTable];
		[controller addAssociation: association];
		[association autorelease];
	}

	/*----------------------------------------------------------------------
	 *	set the qualifier for the fetch.
	 *--------------------------------------------------------------------*/
	[dataSource setQualifier: aQualifier];

	/*----------------------------------------------------------------------
	 *	Start a transaction, do the fetch, rollback the transaction (a fetch
	 *	doesn't need to commit anything) have the values filled to the
	 *	tableView via the columnAssociations.
	 *--------------------------------------------------------------------*/
	[dbContext beginTransaction];

	if (! [controller fetch])
		NSLog(@"controller fetch failed.");

	[dbContext rollbackTransaction];

	/*----------------------------------------------------------------------
	 *	Redisplay everything...
	 *--------------------------------------------------------------------*/
	[resultWindow reenableDisplay];
	[controller redisplay];
	[resultWindow makeKeyAndOrderFront:self];
	return;
}
	
	
- (int) browser:aBrowser fillMatrix:aMatrix inColumn:(int)aColumn
{
	int			iterator = 0, entryCount = 0;
	id			contents = nil;
	id			enumerator, current;

	/*----------------------------------------------------------------------
	 *	Fill qualifier browser with the attributes of the selected entity;
	 *	this is the same list as in the second column of the first browser.
	 *--------------------------------------------------------------------*/
	if (aBrowser == qualifierBrowser) {
		id selectedEntity;

		iterator = 0;
		if ([browser selectedColumn] < 0) 
			return 0;

		selectedEntity = [NSString stringWithCString: 
			[(Cell *)[[browser matrixInColumn:0] selectedCell]
			 stringValue]];
		contents = [[[adaptor model] entityNamed: selectedEntity] attributes];
		entity = nil;

		entryCount = [contents count];
		[aMatrix renewRows: entryCount cols: 1];

		enumerator = [contents objectEnumerator];
		while ((current = [enumerator nextObject]) != nil) {

			[[[aMatrix cellAt: iterator++ : 0] 
				setStringValue: [[(EOAttribute *)current name] cString]]
				setLeaf:  YES];
		}
		return entryCount;
	}

	/*----------------------------------------------------------------------
	 *	The main entity attribute browser with two columns.  One selects the
	 *	entity for the query the other the attributes to be selected.  Note
	 *	that since we're using the EODatabaseAdaptor (instead of the
	 *	EOChannelAdaptor, we actually select the entire object, but only these
	 *	fields will be associated with the tableView.
	 *
	 *	Reset query qualifiers for new entity
	 *--------------------------------------------------------------------*/
	[qualifier autorelease];
	[qualifierText setStringValue: ""];
	[queryText setStringValue: ""];

	if (aColumn == 0) 
		contents = [[adaptor model] entities];

	else if (aColumn == 1) {
		id selectedEntity;

		selectedEntity = [NSString stringWithCString: 
			[(Cell *)[[aBrowser matrixInColumn:0] selectedCell] stringValue]];
		contents = [[[adaptor model] entityNamed: selectedEntity] attributes];
		entity = nil;
	}

	entryCount = [contents count];
	[aMatrix renewRows: entryCount cols: 1];

	enumerator = [contents objectEnumerator];
	while ((current = [enumerator nextObject]) != nil) {

		[[[aMatrix cellAt: iterator : 0] 
			setStringValue: [[(EOEntity *)current name] cString]]
			setLeaf:  aColumn == 1 ];
		iterator++;
	}

	if (aColumn == 	1)
		[[qualifierBrowser loadColumnZero] display];

	return entryCount;
}


- selectOperation:sender
{
	int selectedOp;
	id	opString = nil;
	int	selection;

	selectedOp = [[qualifierOperation selectedCell] tag];

	if (selectedOp == 0) opString = @"<";
	else if (selectedOp == 1) opString = @"<=";
	else if (selectedOp == 2) opString = @">";
	else if (selectedOp == 3) opString = @">=";
	else if (selectedOp == 4) opString = @"=";
	else if (selectedOp == 5) opString = @"<>";
	
	
	[qualifierText setStringValue: 
		[[NSString stringWithFormat:@"%s%s ",
		[(TextField *)qualifierText stringValue],
			[opString cString]] cString]];

	selection = strlen([(TextField *)qualifierText stringValue]);
	[qualifierText selectText:nil];


	return self;
}


- selectAttribute:sender
{
	id			selectedCell;
	EOAttribute	*newAttribute;

	if (entity == nil) {
		const char 	*entityName;

		entityName = [(Cell *)[[browser matrixInColumn: 0]
								selectedCell] stringValue];
		entity = [[adaptor model] entityNamed:
				[NSString stringWithCString: entityName]];
	}

	if ((selectedCell = [qualifierBrowser selectedCell])) {
		id	newString;

		newAttribute = [entity attributeNamed:
			[NSString stringWithCString:[(Cell *)selectedCell stringValue]]];

	/*----------------------------------------------------------------------
	 *	Get the name internal to the database; if it is a column, get the
 	 *	column name, otherwise, it is a complex value (a flattened attribute)
	 *	so get the description.
	 *--------------------------------------------------------------------*/
		newString = [NSString stringWithFormat: @"%s%s ",
			[(TextField *)qualifierText stringValue], 	
			[newAttribute columnName] ? 	
				[(NSString *)[newAttribute columnName] cString] :
				[(NSString *)[newAttribute definition] cString]];

		[qualifierText setStringValue: [newString cString]];
		[qualifierText selectText: nil];

	}

	return self;
}


- replaceQualifier: sender
{
	if (qualifier) [qualifier autorelease];

	if (entity == nil) {
		const char 	*entityName;

		entityName = [(Cell *)[[browser matrixInColumn: 0]
								selectedCell] stringValue];
		if (! entityName) {
			NXRunAlertPanel("Query Builder",
				"A model file must be present before a query is possible.",
				NULL, NULL, NULL);
			return nil;
		}

		entity = [[adaptor model] entityNamed:
				[NSString stringWithCString: entityName]];
	}

	qualifier = [[EOQualifier allocWithZone:[self zone]]
		initWithEntity:entity
		qualifierFormat: [NSString stringWithCString:
			[(TextField *)qualifierText stringValue]]];
	
	if ([negator state]) [qualifier negate];

	[negator setState: 0];
	[queryText setStringValue:[[qualifier contents] cString]];
	[qualifierText setStringValue:""];
	[qualifierText selectText: nil];
	[qualifierOperation selectCellAt:-1:-1];


	return self;
}


- appendAND: sender
{
	id newQualifier;

	if (entity == nil) {
		const char 	*entityName;

		entityName = [(Cell *)[[browser matrixInColumn: 0]
								selectedCell] stringValue];
		if (! entityName) {
			NXRunAlertPanel("Query Builder",
				"A model file must be present before a query is possible.",
				NULL, NULL, NULL);
			return nil;
		}

		entity = [[adaptor model] entityNamed:
				[NSString stringWithCString: entityName]];
	}


	newQualifier = [[EOQualifier allocWithZone:[self zone]]
		initWithEntity:entity
		qualifierFormat: [NSString stringWithCString:
			[(TextField *)qualifierText stringValue]]];

	/*----------------------------------------------------------------------
	 *	If the qualifier is to be negated...
	 *--------------------------------------------------------------------*/
	if ([negator state]) [newQualifier negate];

	/*----------------------------------------------------------------------
	 *	AND the new qualifier with the previous one
	 *--------------------------------------------------------------------*/
	[qualifier 	conjoinWithQualifier: newQualifier];

	[newQualifier autorelease];

	[negator setState: 0];
	[queryText setStringValue:[[qualifier contents] cString]];
	[qualifierText setStringValue:""];
	[qualifierText selectText: nil];
	[qualifierOperation selectCellAt:-1:-1];


	return self;
}


- appendOR: sender
{
	id newQualifier;

	if (entity == nil) {
		const char 	*entityName;

		entityName = [(Cell *)[[browser matrixInColumn: 0]
								selectedCell] stringValue];
		if (! entityName) {
			NXRunAlertPanel("Query Builder",
				"A model file must be present before a query is possible.",
				NULL, NULL, NULL);
			return nil;
		}

		entity = [[adaptor model] entityNamed:
				[NSString stringWithCString: entityName]];
	}


	newQualifier = [[EOQualifier allocWithZone:[self zone]]
		initWithEntity:entity
		qualifierFormat: [NSString stringWithCString:
			[(TextField *)qualifierText stringValue]]];

	/*----------------------------------------------------------------------
	 *	If the qualifier is to be negated...
	 *--------------------------------------------------------------------*/
	if ([negator state]) [newQualifier negate];

	/*----------------------------------------------------------------------
	 *	OR the new qualifier with the previous one.
	 *--------------------------------------------------------------------*/
	[qualifier 	disjoinWithQualifier: newQualifier];

	[newQualifier autorelease];

	[negator setState: 0];
	[queryText setStringValue:[[qualifier contents] cString]];
	[qualifierText setStringValue:""];
	[qualifierText selectText: nil];
	[qualifierOperation selectCellAt:-1:-1];


	return self;
}


- clearQualifier: sender
{
	[qualifier autorelease];
	qualifier = nil;
	[negator setState: 0];
	[queryText setStringValue:""];
	[qualifierText setStringValue:""];
	[qualifierText selectText:nil];
	[qualifierOperation selectCellAt:-1:-1];

	return self;
}


@end
